// aa-aa 标签名
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
//aa:aa
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
// 标签开头的正则 捕获的内容是标签名 例如:<div:aa命名空间标签 或<aa
const startTagOpen = new RegExp(`^<${qnameCapture}`);
// 匹配标签结尾的   例如:</div> 或<div:aa>命名空间标签
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);

// 匹配属性的 例如:style="xxx"  style='xxx' style=xxx
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;

// 匹配标签结束的 >
const startTagClose = /^\s*(\/?)>/;



//解析html模板字符串=>ast语法树(解析如下)
/* 
    //html结构
    <div id="app" style="color:red">
        <ul>
            <li>{{name}}</li>
            <li>{{mes}}</li>
        </ul>
    </div> 
    //ast语法树
    {
        tag:'div',
        type:1,
        attrs:[{style:'color:red'},{id:app}],
        children:[
            { 
                tag:'div', 
                type:1,
                attrs:[],
                children:[],
                parent:div
            }]
            parent:null
    }
*/


//定义解析html函数
export function parseHTML(html) {
    //定义创建ast语法树函数
    function createASTElement(tag, attrs) {
        return {
            tag,
            type: 1,
            children: [],
            attrs,
            parent: null,
        }
    }
    //根
    let root = null;
    //当前父元素
    let currentParent;
    //定义栈
    let stack = [];

    //start标签
    function start(tagName, attrs) {
        //console.log('开始:' + tagName);
        //创建ast语法树对象
        let element = createASTElement(tagName, attrs)
        //如果没有根节点
        if (!root) {
            //当前的ast语法树对象为根
            root = element;
        }
        //记录父元素
        currentParent = element
        //栈中注入这个元素
        stack.push(element)
    }
    //end标签
    function end(tagName) {
        //console.log('结尾:' + tagName);
        //如果匹配的到end标签,这个栈中弹出这个元素,
        let element = stack.pop();
        //当前父元素是这个栈中的最后的元素
        currentParent = stack[stack.length - 1];
        //如果这个父元素存在
        if (currentParent) {
            //记录父元素给栈中弹出的元素
            element.parent = currentParent;
            //记录子元素给这个父元素
            currentParent.children.push(element)
        }
    }
    //文本标签 
    function chars(text) {
        //console.log('文本', text);
        //替换文本中的空格为空
        text = text.replace(/\s/g, '');
        if (text) {
            //如果文本存在,这个父节点添加这个文本
            currentParent.children.push({ type: 3, text })
        }
    }
    //截取html  xxx.substring(n) 从索引值n开始,截取到最后,并且返回被截取的html 
    function advance(n) {
        html = html.substring(n)
    }
    //解析start标签
    function parseStartTag() {
        //正则标签开头的正则 捕获的内容是标签名 例如:<div:aa命名空间标签 或<aa
        const start = html.match(startTagOpen)
        if (start) {
            //ast语法树对象的零件
            let match = {
                tagName: start[1],
                attrs: []
            }
            advance(start[0].length)//元素获取
            //console.log(html,match);
            //属性获取
            let end, attr;
            //循环:判断不是结束闭合标签,并且匹配得到属性
            while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
                //获取完属性,删除html上的属性/方法
                advance(attr[0].length)
                //console.log(attr, html)
                //ast语法树对象的零件---attrs
                match.attrs.push({ name: attr[1], value: attr[3] || attr[4] || attr[5] || true })
            }
            //如果是闭合结束标签
            if (end) {
                //将匹配到的'>'切割掉
                advance(end[0].length)
                //最终返回这个ast语法树
                return match
            }
        }
    }
    while (html) {
        //查找'<'的索引值
        let txtEnd = html.indexOf('<');
        //索引值为0的, "<"
        if (txtEnd == 0) {
            // 调用解析 start闭合标签
            let startTagMatch = parseStartTag();
            //debugger;
            if (startTagMatch) {
                start(startTagMatch.tagName, startTagMatch.attrs)
                continue;
            }
            //解析
            let endTagMatch = html.match(endTag)
            //end结束标签
            if (endTagMatch) {
                advance(endTagMatch[0].length);
                end(endTagMatch[1])
                continue;
            }
        }
        //索引值大于0 ,解析文本节点('      ')  
        let test;
        if (txtEnd > 0) {
            test = html.substring(0, txtEnd);
        }
        if (test) {
            advance(test.length);
            chars(test)
        }
    }
    return root
}